/*
 * Copyright (C) 2020 ActiveJ LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.activej.http;

import org.intellij.lang.annotations.Language;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This is a util class, which provides a mean to render any exception into an {@link HttpResponse}
 * with the stacktrace rendered nicely.
 * It also generates a link to the IntelliJ IDEA REST API (<a href="http://localhost:63342">...</a> + port_offset) from the stacktrace,
 * just like IDEA does in its log console.
 */
public final class DebugStacktraceRenderer {
	private static final String IDEA_REST_API_STARTING_PORT = "63342";

	@Language("HTML")
	private static final String DEBUG_SERVER_ERROR_HTML = """
		<!doctype html>
		<html lang="en">
			<head>
				<meta charset="UTF-8">
				<title>{title}</title>
				<style>
					html, body {
						height: 100%;
						margin: 0;
						padding: 0;
					}

					h1, p {
						font-family: sans-serif;
					}

					.link {
						color: #00E;
						text-decoration: underline;
						cursor: pointer;
					}
				</style>
			</head>
			<body>
				<script>
					window.onload = () => {
						function check(portOffset) {
							if (portOffset > 10) return Promise.reject('no running intellij idea instance found');
							return fetch('http://localhost:' + ($ideaRestApiPort + portOffset))
								.then(r => r.text())
								.then(t => t.includes('IDEA') ? ($ideaRestApiPort + portOffset) : check(portOffset + 1), () => check(portOffset + 1));
						}

						check(0).then(port => document.querySelectorAll('[data-target]').forEach(a => {
							a.onclick = () => fetch('http://localhost:' + port + '/' + a.dataset.target);
							a.classList.add('link');
						}));
					};
				</script>
				<div style="position:relative;min-height:100%;">
					<h1 style="text-align:center;margin-top:0;padding-top:0.5em;">{title}</h1>
					<hr style="margin-left:10px;margin-right:10px;">
					<pre style="color:#8B0000;font-size:1.5em;padding:10px 10px 4em;">{stacktrace}</pre>
					<div style="position:absolute;bottom:1px;width:100%;height:4em">
						<hr style="margin-left:10px;margin-right:10px">
						<p style="text-align:center;">ActiveJ $activejVersion</p>
					</div>
				</div>
			</body>
		</html>
		"""
		.replace("$ideaRestApiPort", IDEA_REST_API_STARTING_PORT)
		.replace("$activejVersion", HttpExceptionFormatter.ACTIVEJ_VERSION);

	private static final Pattern STACK_TRACE_ELEMENT;

	static {
		String ident = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
		STACK_TRACE_ELEMENT = Pattern.compile(
			"(at ((?:" + ident + "\\.)+)" + ident +
			"\\()(" + ident + "(\\." + ident + ")(:\\d+)?)\\)");
	}

	public static HttpResponse.Builder render(Exception e, int code) {
		StringWriter writer = new StringWriter();
		e.printStackTrace(new PrintWriter(writer));
		Matcher matcher = STACK_TRACE_ELEMENT.matcher(writer.toString());
		StringBuilder stacktrace = new StringBuilder();
		while (matcher.find()) {
			String cls = matcher.group(2);
			String quotedFile = Matcher.quoteReplacement(cls.substring(0, cls.length() - 1)
				.replace('.', '/')
				.replaceAll("\\$.*(?:\\.|$)", ""));
			matcher.appendReplacement(stacktrace,
				"$1<a data-target=\"api/file/" + quotedFile + "$4$5\">$3</a>)");
		}
		matcher.appendTail(stacktrace);
		return HttpResponse.ofCode(code)
			.withHtml(DEBUG_SERVER_ERROR_HTML
				.replace("{title}", HttpUtils.getHttpErrorTitle(code))
				.replace("{stacktrace}", stacktrace));
	}
}
